#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
token password 验证
pip install python-jose
pip install passlib
pip install bcrypt

"""
from datetime import datetime, timedelta
from typing import Any, Union, Dict

from jose import jwt
from passlib.context import CryptContext
from pydantic import ValidationError

from ..common.api_response import ResponseEnum, BusinessException
from ..settings import settings

PASSWORD_CONTEXT = CryptContext(schemes=["bcrypt"], deprecated="auto")


def create_access_token(
    subject: Union[str, Any], user_id: str, expires_delta: timedelta = None
) -> str:
    """
    生成token
    :param subject:需要存储到token的数据(注意token里面的数据，属于公开的)
    :param user_id: 用户id
    :param expires_delta:
    :return:
    """
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(
            minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
        )
    to_encode = {"exp": expire, "subject": str(subject), "id": user_id}
    encoded_jwt = jwt.encode(
        to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM
    )
    return encoded_jwt


def check_jwt_token(token: str) -> Dict[str, Any]:
    """
    解析验证token  默认验证headers里面为token字段的数据
    可以给 headers 里面token替换别名, 以下示例为 X-Token
    token: Optional[str] = Header(None, alias="X-Token")
    :param token:
    :return:
    """
    try:
        payload = jwt.decode(
            token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
        )
        return payload
    except jwt.ExpiredSignatureError:
        raise BusinessException(ResponseEnum.TOKEN_ERROR)
    except (jwt.JWTError, ValidationError, AttributeError):
        raise BusinessException(ResponseEnum.TOKEN_ERROR)


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """
    验证密码
    :param plain_password: 原密码
    :param hashed_password: hash后的密码
    :return:
    """
    return PASSWORD_CONTEXT.verify(plain_password, hashed_password)


def get_password_hash(password: str) -> str:
    """
    获取 hash 后的密码
    :param password:
    :return:
    """
    return PASSWORD_CONTEXT.hash(password)
